/*
 * main.c
 *
 *  Created on: Mar 4, 2012
 *      Author: gg, dd
 *
 *      Collects user input:
 *      	Type of process ___
 *      		Number of those processes
 *
 *      	Scheduling type
 *
 *		starts the program
 *			Initialize
 *				Memory
 *				system_timer
 *				keyboard (listener)
 *				cpu *(with Scheduler ? )
 *				I/O device ?
 *					No - initialized by CPU on trap
 *				Scheduler
 *
 *			Create Processes
 *
 */

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include "CPU/cpu.h"
#include "IO Devices/io_device.h"
#include "Scheduler/scheduler.h"
#include "Shared Memory/memory.h"
#include "System Timer/system_timer.h"
#include "Processes/process.h"
#include "Processes/process_type.h"

/**
 * Globals
 */
cpu my_proc;
io_device my_keyboard;
io_device my_aux_io;
scheduler* my_task_scheduler;
memory* my_ram;
system_timer my_timer;
process** my_processes;
Queue my_interrupt_q;
int my_operating_mode;
/**
 * End Globals
 */


int INTERRUPT_INTERVAL_LENGTH = 10;
int CONSUMER_PRODUCER_REQ_SIZE = 100;
int CONSUMER_PRODUCER_STEPS = 1000;
int GENERAL_PROCESS_STEPS = 1000;
int GENERAL_PROCESS_REQS = 100;
int PROCESS_COUNT = 0;
int RAM_SIZE = 1000;

typedef struct timer_struct {
	// Parameters required to initialize a System Timer.
	system_timer* the_timer;
	cpu* proc;
} timer_struct;

typedef struct cpu_struct{
	// Parameters required to initialize a CPU.
	scheduler* the_task_scheduler;
	memory* the_ram;
	process** the_processes;
} cpu_struct;

void *start_timer(system_timer* the_timer){
	system_timer_run(the_timer);
	return NULL;
}

void *start_cpu(cpu* the_cpu){
	run(the_cpu);
	return NULL;
}

void *start_keyboard(io_device* the_keyboard){
	run_io(the_keyboard);
	return NULL;
}

void *start_aux_io(io_device* the_aux_io){
	run_io(the_aux_io);
	return NULL;
}

int* getaddresses(){
	int* addresses;
	int i;
	addresses = (int*)malloc(sizeof(int)*CONSUMER_PRODUCER_REQ_SIZE);
	for (i = 0; i < CONSUMER_PRODUCER_REQ_SIZE; i++){
		int done = 0;
		int memaddress;
		while(!done){  // Don't use Duplicate Memory Addresses
			memaddress = rand() % (my_ram->size - 1);
			done = isdone(memaddress, addresses);
		}
		addresses[i] = memaddress;
	}
	return addresses;
}

void user_input(){
	// Gather number of Calculators
	// Gather number of Producer/Consumer Pairs
	// There is ONE Keyboard
	// There is ONE Aux IO Device
	// Gather the Type of Scheduler
	char input[1024];
	int numb_crunch_count = 0;
	int producer_count = 0;
	int consumer_count = 0;
	int user_interface_count = 0;
	Queue producerqueue = CreateQueue(10);
	Queue consumerqueue = CreateQueue(10);

	printf("\nEnter the Pattern of Processes:");
	printf("\n\n\t R : Round Robin\n\t L : Lottery \n\t P : Priority\n");
	printf(" \n\t p : Producer\n\t c : Consumer\n\t n : Number Cruncher\n\t u : User Interface\n\n\t Example 'Lppcncu'\n ");
	printf("\n Keep in mind you will want a 1:1 ratio of Producers and Consumers\n");
	scanf("%s", input);
	//input[0] = 'L';
	//input[1] = 'p';
	//input[2] = 'c';
	//input[0] = 'n';
	// Flushing the Input - if you don't do this, the Keyboard will Trap an Interrupt Immediately from the leftover Data in the buffer
	fflush(stdin);
	srand(time(NULL));

	my_processes = malloc(sizeof(process) * 1024); // Max Char Input is 1024...  This is awful way to do it... but works for now...

	int j;
	int i = 0;
	for (j = 0; j < sizeof(input); j++){
		if (input[j] == 'p' || input[j] == 'c'){
			// Create Memory Addresses for a Pair of P/C store in Array
			int* addresses;
			// Instantiate the Producer & Consumer with those Addresses
			if (input[j] == 'p'){
				if (IsEmpty(consumerqueue)){
					// No consumer before me has created an array of addresses to utilize.
					addresses = getaddresses();
					Enqueue(addresses, producerqueue);
				} else {
					// A consumer before me stored an array for me to use! Sweet!
					addresses = (int*)FrontAndDequeue(consumerqueue);
				}
				process* producer = process_init(CONSUMER_PRODUCER_STEPS, CONSUMER_PRODUCER_REQ_SIZE, PRODUCER, i, addresses, my_ram->size);
				my_processes[i] = producer;
				i++;
				producer_count += 1;
			} else if (input[j] == 'c'){
				if (IsEmpty(producerqueue)){
					// No producer before me has created an array of addresses to utilize.
					addresses = getaddresses();
					Enqueue(addresses, consumerqueue);
				} else {
					// A producer before me stored an array for me to use!  Sweet!
					addresses = (int*)FrontAndDequeue(producerqueue);
				}
				process* consumer = process_init(CONSUMER_PRODUCER_STEPS, CONSUMER_PRODUCER_REQ_SIZE, CONSUMER, i, addresses, my_ram->size);
				my_processes[i] = consumer;
				i++;
				consumer_count += 1;
			}

		} else if (input[j] == 'n'){
			numb_crunch_count += 1;
			process* numb_crunch = process_init(GENERAL_PROCESS_STEPS, GENERAL_PROCESS_REQS, NUMBER_CRUNCHER, i, NULL, my_ram->size);
			my_processes[i] = numb_crunch;
			i++;

		} else if (input[j] == 'u'){
			user_interface_count += 1;
			process* user_interface = process_init(GENERAL_PROCESS_STEPS, GENERAL_PROCESS_REQS, USER_INTERFACE, i, NULL, my_ram->size);
			my_processes[i] = user_interface;
			i++;
		} else if (input[j] == 'R'){
			//0: round-robin
			my_operating_mode=0;
		} else if (input[j] == 'P'){
			//1: for priority
			my_operating_mode=1;
		} else if (input[j] == 'L'){
			//2: Lottery
			my_operating_mode=2;
		} else {
			printf("\nStopping at Invalid Input, or End of line\n");
			printf("P: %d C: %d N: %d U: %d\n\n", producer_count, consumer_count, numb_crunch_count, user_interface_count);
			break;
		}
	}

	PROCESS_COUNT = i;
}

void print_status(){
	int i;
	for (i = 0; i < PROCESS_COUNT; i++){
		char* name = "";
		switch(my_processes[i]->type){
			case 0:
				name = "Number Cruncher";
				break;
			case 1:
				name = "Consumer";
				break;
			case 2:
				name = "Producer";
				break;
		}
		printf("MAIN: Process ID: %d (%s)\n", my_processes[i]->pcb.process_id, name);
	}
}
void launch_threads(){

	pthread_t start_timer_thread;
	pthread_t start_cpu_thread;
	pthread_t start_keyboard_thread;
	pthread_t start_aux_io_thread;

	// Create and Init the Interrupt Queue
	my_interrupt_q = CreateQueue(100);

	// Initialize the IO Devices.
	io_device_init(&my_keyboard, my_interrupt_q, 1);
	io_device_init(&my_aux_io, my_interrupt_q, 0);

	// Create and Initialize the Scheduler
	my_task_scheduler = scheduler_init(my_operating_mode, my_processes, PROCESS_COUNT);
	// Init the CPU

	// Create & Init the CPU which uses IO Devices and Scheduler
	cpu_init(&my_proc, my_task_scheduler, my_ram, my_processes, PROCESS_COUNT, CONSUMER_PRODUCER_REQ_SIZE,
			my_interrupt_q, &my_keyboard, &my_aux_io);

	// Create & Init the SYstem timer which uses CPU
	system_timer_init(&my_timer, INTERRUPT_INTERVAL_LENGTH, &my_proc);

	// Create & Initialize the Keyboard, this is its own thread.
	pthread_create(&start_keyboard_thread, NULL, start_keyboard, &my_keyboard);

	// Create & Init the aux IO
	pthread_create(&start_aux_io_thread, NULL, start_aux_io, &my_aux_io);

	// Create, Init and Thread System Timer.
	pthread_create(&start_timer_thread, NULL, start_timer, &my_timer);

	// Create & Initialize the CPU,  This will need it's own Thread.
	pthread_create(&start_cpu_thread, NULL, start_cpu, &my_proc);


	// Wait for the threads to finish.
	pthread_join(start_timer_thread, 0);
	pthread_join(start_cpu_thread, 0);
	pthread_join(start_keyboard_thread, 0);
	pthread_join(start_aux_io_thread, 0);
}

int main(){
	// Initialize Memory:
	my_ram = memory_init(RAM_SIZE);

	// Prompt User Interface Portion.
	user_input();
	// End Prompt Session

	// Details for Debug really...
	//print_status();

	// Kick off threads
	launch_threads();

	return 0;
}


